<?php
/**
 * DefaultController.php
 *
 * Contains all the supporting classes and methods that make the dashboard work
 * @package PHPZendOpcacheBundle
 * @author Steven King <kingrst@gmail.com>
 * @copyright Copyright (C) 2014, Steven King
 * @license http://opensource.org/licenses/GPL-3.0 GNU General Public License, version 3
 */
 
namespace PHP\ZendOpcacheBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;

use PHP\ZendOpcacheBundle\Form\File\SelectFile;

use Nelmio\ApiDocBundle\Annotation\ApiDoc;

/**
 * DefaultController Class for PHPZendOpcacheBundle
 *
 * DefaultController contains all of the basic methods that make the dashboard work
 * @package PHPZendOpcacheBundle\Controller
 * @subpackage DefaultController
 * @category class
 * @author Steven King <kingrst@gmail.com>
 */
class DefaultController extends Controller {
  /**
   * {@inheritdoc}
   */

  /**#@+
   * @access protected
   * @var array
   */
  /**
   * Holds status information about the Opcache module
   */
  protected $opcache_status = array();
  /**
   * Holds configuration information about the Opcache module
   */
  protected $opcache_configuration = array();
  /**
   * Used to pass generic and redundant information to the TWIG templating engine
   */
  protected $default_data = array();
  /**#@-*/

  /**#@+
   * @access protected
   * @var string
   */
  /**
   * Stores the software name as reported by the Opcache module
   */
  protected $opcache_product_name;
  /**
   * Stores the Opcache module version
   */
  protected $opcache_version;
  /**
   * Stores the PHP ZendOpcache Dashboard version
   */
  protected $dashboard_version = "1.0_beta";
  /**#@-*/

  /**
   * Holds the user session used to store an array of inodes => file_paths when invalidating scripts
   * @access protected
   * @var session
   */
  protected $session;

  /**
   * DefaultController class constructor
   */
  public function __construct() {
    /**
     * Populate $opcache_status
     * @see DefaultController::$opcache_status
     */
    $this->opcache_status = opcache_get_status();
    /**
     * Populate $opcache_configuration
     * @see DefaultController::$opcache_configuration
     */
    $this->opcache_configuration = opcache_get_configuration();
    /**
     * Poulate $opcache_product_name
     * @see DefaultController::$opcache_product_name
     */
    $this->opcache_product_name = $this->opcache_configuration['version']['opcache_product_name'];
    /**
     * Populate $opcache_version
     * @see DefaultController::$opcache_version
     */
    $this->opcache_version = $this->opcache_configuration['version']['version'];

    /**
     * Populate $default_data
     * @see DefaultController::$default_data
     */
    $this->default_data = array('version' => $this->opcache_version, 'product_name' => $this->opcache_product_name, 'db_version' => $this->dashboard_version);
  }

  /**
   * function bytesToMB to convert values in bytes to Megabytes (@link $bytes)
   * @access private
   * @param integer|float $bytes
   * @return integer|float
   */
  private function bytesToMB( $bytes ) {
    /**
     * Stores converted value
     * $var integer|float
     */
    $nowMB = round( $bytes/1024/1024, 2 );

    return $nowMB;
  }

  /**
   * function bytesToKB to convert values in bytes to Kilobytes (@link $bytes)
   * @access private
   * @param integer|float $bytes
   * @return integer|float
   */
  private function bytesToKB( $bytes ) {
    /**
     * Stores converted value
     * $var integer|float
     */
    $nowKB = round( $bytes/1024, 2 );

    return $nowKB;
  }

  /**
   * function indexAction renders the index page
   * @access public
   * @return response
   */
  public function indexAction() {
    # Make a copy of the status array so that we do not lose the original values
    /**
     * Stores a copy of the member variable $opcache_status as to not lose the original values
     * @var array
     */
    $opcache_status = $this->opcache_status;
    /**
     * Stores a copy of the member variable $opcache_configuration as to not lose the original values
     * @var array
     */
    $opcache_configuration = $this->opcache_configuration;

    $opcache_status['opcache_enabled'] = ( $this->opcache_status['opcache_enabled'] == true ? "Enabled" : "Disabled" );
    $opcache_status['cache_full'] = ( $this->opcache_status['cache_full'] == false ? "Not Full" : "Full!" );
    $opcache_status['restart_pending'] = ( $this->opcache_status['restart_pending'] == false ? "No" : "Yes" );
    $opcache_status['restart_in_progress'] = ( $this->opcache_status['restart_in_progress'] == false ? "No" : "Yes");

    $opcache_status['memory_usage']['used_memory'] = $this->bytesToMB( $this->opcache_status['memory_usage']['used_memory'] );
    $opcache_status['memory_usage']['free_memory'] = $this->bytesToMB( $this->opcache_status['memory_usage']['free_memory'] );
    $opcache_status['memory_usage']['wasted_memory'] = $this->bytesToMB( $this->opcache_status['memory_usage']['wasted_memory'] );

    $opcache_configuration['directives']['opcache.memory_consumption'] = $this->bytesToMB( $this->opcache_configuration['directives']['opcache.memory_consumption'] );

    $opcache_status['memory_usage']['current_wasted_percentage'] = round( $this->opcache_status['memory_usage']['current_wasted_percentage'], 2 );

    $opcache_status['opcache_statistics']['start_time'] = date(\DateTime::RFC822 , $this->opcache_status['opcache_statistics']['start_time']);
    $opcache_status['opcache_statistics']['last_restart_time'] = ( $this->opcache_status['opcache_statistics']['last_restart_time'] == 0 ? "Never" : date(\DateTime::RFC822, $this->opcache_status['opcache_statistics']['last_restart_time']) );
    $opcache_status['opcache_statistics']['opcache_hit_rate'] = round( $this->opcache_status['opcache_statistics']['opcache_hit_rate'], 2);

    # Package up our variables in a nice little array to feed to TWIG
    /**
     * Collects all the data that we need to send to the TWIG template (@link $default_data)
     * @var array
     */
    $data = array('status' => $opcache_status, 'config' => $opcache_configuration, 'info' => $this->default_data);

    return $this->render('PHPZendOpcacheBundle:Default:index.html.twig', $data);
  }

  public function raw_statsAction() {
    $data = array('opcache_status_string' => print_r($this->opcache_status, true), 'opcache_config_string' => print_r($this->opcache_configuration, true), 'info' => $this->default_data);

    return $this->render('PHPZendOpcacheBundle:Default:raw_stats.html.twig', $data);
  }

  public function list_scriptsAction() {
    # Only create a new session if one doesn't already exist
    if(!isset($this->session)) {
      $this->session = new Session();
    }

    # Only start a new session if one isn't already started
    if(!$this->session->isStarted()) {
      $this->session->start();
    }

    # We'll want to start with a clean inode table each time this list is loaded
    $inode_table = array();
    $this->session->remove('inode_table');

    # Ensure the statcache is cleared
    clearstatcache();

    $scripts = $this->opcache_status['scripts'];

    # Convert timestamps and memory to human reaable values
    foreach ( $scripts as $script ) {
      $script['last_used_timestamp'] = date( 'Y-m-d H:m', $script['last_used_timestamp'] );
      $script['timestamp'] = date( 'Y-m-d H:m', $script['timestamp'] );

      $script['memory_consumption'] = $this->bytesToKB( $script['memory_consumption'] );

      # Build and inode table so we don't send filenames through the URL
      # Check the return code of fileinode, if false, nothing is added to the array. We just assume
      # that the file was removed before we had a chance to get the inode number.
      if(file_exists($script['full_path'])) {
        $inode = fileinode($script['full_path']);
        $inode_table[$inode] = $script['full_path'];

        # Also store the inode in the scripts array for generating URLs
        $script['file_inode'] = $inode;

        $scripts[$script['full_path']] = $script;
      }
    }

    # Serialize our inode_table array so that it can go inside a session
    $sinode_table = serialize($inode_table);
    $this->session->set('inode_table', $sinode_table);
    $data = array('scripts' => $scripts, 'info' => $this->default_data);

    return $this->render('PHPZendOpcacheBundle:Default:list_scripts.html.twig', $data);
  }

  public function reset_cacheAction() {
    $success = opcache_reset();

    $data = array('success' => $success, 'info' => $this->default_data);

    return $this->render('PHPZendOpcacheBundle:Default:reset_cache.html.twig', $data);
  }

  /**
   * @ApiDoc(
   *   description="Invalidates the Opcahce'ed copy of a script from memory",
   *   requirements={
   *     {"name"="file_inode", "dataType"="integer", "requirement"="\d+", "description"="Inode of the script to be invalidated."}
   *   },
   *   parameters={
   *     {"name"="file_inode", "dataType"="integer", "required"=true, "description"="Inode of the script tp be invalidated."}
   *   }
   * )
   */
  public function invalidate_scriptAction($file_inode) {
    # If the session variable is not set, set it.
    if(!isset($this->session)) {
      $this->session = new Session();
    }

    # A session should already be started by the list scripts page!
    if(!$this->session->isStarted()) {
      $this->session->start();
    }

    # Everything should be set with the session, pull in the dataz
    $inode_table = unserialize($this->session->get('inode_table'));

    # Attempt to invalidate the script
    if(isset($inode_table[$file_inode])) {
      $invalid = opcache_invalidate($inode_table[$file_inode], true);
    } else {
      # Set to two to display a "file is not in cache message"
      $invalid = 2;

      # Set a key of $file_inode to the inode attempting to be cleared. This way we have some sort of reference.
      $inode_table[$file_inode] = $file_inode;
    }

    $data = array('script' => $inode_table[$file_inode], 'success' => $invalid, 'info' => $this->default_data);

    # Cleanup the session information before rendering the page
    $this->session->clear();
    $this->session->invalidate();
    $this->session = null;

    return $this->render('PHPZendOpcacheBundle:Default:invalidate_script.html.twig', $data);
  }

  public function compile_scriptAction() {
    $file_path = new SelectFile();
    $form = $this->createForm('file', $file_path);

    if($request->getMethod() == 'POST') {
      $form->handleRequest($request);

      if($form->isValid()) {
        $form_data = $form->getData();

        $compile_status = opcache_compile_file($form_data['file_path']);

        $data = array( 'display_form' => false, 'success' => $compile_status, 'file_path' => $form_data['file_path'], 'info' => $this->default_data);

      }
    } else {
      $view = $form->createView();

      $data = array( 'display_form' => true, 'form_view' => $view, 'info' => $this->default_data);
    }

    return $this->render('PHPZendOpcacheBundle:Default:compile_script.html.twig', $data);
  }
}
